using System;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Diagnostics;
using System.Xml.Linq;
using CSW.Framework.Common.Binding.ValueTypes;
using CSW.Framework.UI.Binding;
using CSW.Framework.Common;
using LinqBusinessObjectBuilder.Generator;
using LinqBusinessObjectBuilder.Options;
using LinqBusinessObjectBuilder.ProjectInfo;

namespace LinqBusinessObjectBuilder.Forms
{
    internal partial class MainForm : Form
    {
        private Binder<Project> m_Binder;
        private Project m_Project;

        public MainForm()
        {
            InitializeComponent();

            openFileDialog.InitialDirectory = Program.AppDataPath;
            saveFileDialog.InitialDirectory = Program.AppDataPath;

            string lastProjectFileName = GlobalOptions.Instance.CurrentProjectPath;
            if (File.Exists(lastProjectFileName))
                LoadProject(lastProjectFileName);
            else
                LoadProject(new Project());
        }

        private void LoadProject(string projectFileName)
        {
            Guard.ArgumentNotNullOrEmptyString(projectFileName, "projectFileName");

            Cursor.Current = Cursors.WaitCursor;
            LoadProject(Project.LoadFromFile(projectFileName));
            Cursor.Current = Cursors.Default;
        }

        private void LoadProject(Project project)
        {
            Guard.ArgumentNotNull(project, "project");

            Cursor.Current = Cursors.WaitCursor;

            m_Project = project;

            if (!string.IsNullOrEmpty(m_Project.ProjectFilePath))
            {
                GlobalOptions.Instance.BeginEdit();
                GlobalOptions.Instance.CurrentProjectPath = m_Project.ProjectFilePath;
                GlobalOptions.Instance.Save();
            }

            if (m_Binder != null)
                m_Binder.Dispose();

            m_Binder = new Binder<Project>(m_Project, new ErrorWrapper(errorProvider1));
            m_Binder.Bind(new BindableTextBox(txtServerName), p => p.Server);
            m_Binder.Bind(new BindableTextBox(txtUserName), p => p.User);
            m_Binder.Bind(new BindableTextBox(txtPassword), p => p.Password);
            m_Binder.Bind(new BindableComboBox(cmbAuthentication, AuthenticationMode.GetAll(), false), p => p.AuthenticationMode);
            m_Binder.Bind(new BindableComboBox(cmbDatabase, m_Project.DatabaseList, false), p => p.Database);
            m_Binder.Bind(new BindableTextBox(txtNamespace), p => p.Namespace);
            m_Binder.Bind(new BindableTextBox(txtContextClass), p => p.DataContextName);
            m_Binder.Bind(new BindableComboBox(cmbSerialization, SerializationMode.GetAll(), false), p => p.SerializationMode);
            m_Binder.Bind(new BindableCheckBox(chkPluralize), p => p.UsePluralize);
            m_Binder.Bind(new BindableCheckBox(chkDatabaseStoredProcedures), p => p.GenerateStoredProcedures);
            m_Binder.Bind(new BindableCheckBox(chkDatabaseViews), p => p.GenerateViews);
            m_Binder.Bind(new BindableCheckBox(chkDatabaseFunctions), p => p.GenerateFunctions);
            m_Binder.Bind(new BindableTextBox(txtOutputFolder), p => p.OutputPath);
            m_Binder.Bind(new BindableTextBox(txtSolutionName), p => p.SolutionName);

            m_Binder.UpdateControls();

            if (!m_Project.IsEditting)
                m_Project.BeginEdit();

            Cursor.Current = Cursors.Default;
        }

        private void btnTestConnection_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            try
            {
                m_Project.RefreshDatabases();
            }
            catch (SqlException ex)
            {
                Cursor.Current = Cursors.Default;
                MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            Cursor.Current = Cursors.Default;
        }

        private void brnBrowseOutputFolder_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            using (FolderBrowserDialog folder = new FolderBrowserDialog())
            {
                folder.ShowNewFolderButton = true;
                Cursor.Current = Cursors.Default;
                if (folder.ShowDialog() == DialogResult.OK)
                {
                    m_Project.OutputPath = folder.SelectedPath;
                }
            }
        }

        private void mnuOptions_Click(object sender, EventArgs e)
        {
            ShowOptionsForm();
        }

        private void ShowOptionsForm()
        {
            Cursor.Current = Cursors.WaitCursor;
            using (GlobalOptionsForm form = new GlobalOptionsForm())
            {
                Cursor.Current = Cursors.Default;
                form.ShowDialog();
            }
        }

        private void mnuGenerate_Click(object sender, EventArgs e)
        {
            Application.DoEvents();

            // since we have a keyboard shortcut here..
            m_Binder.ValidateAllControls();

            if (string.IsNullOrEmpty(m_Project.Server))
            {
                MessageBox.Show("Please enter the server name.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                txtServerName.Focus();
                return;
            }

            if (string.IsNullOrEmpty(m_Project.Database))
            {
                MessageBox.Show("Please select the database.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                cmbDatabase.Focus();
                return;
            }

            if (string.IsNullOrEmpty(m_Project.Namespace))
            {
                MessageBox.Show("Please enter a namespace.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                txtNamespace.Focus();
                return;
            }

            if (string.IsNullOrEmpty(m_Project.DataContextName))
            {
                MessageBox.Show("Please enter a data context name.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                txtContextClass.Focus();
                return;
            }

            if (string.IsNullOrEmpty(m_Project.OutputPath))
            {
                MessageBox.Show("Please enter the output path.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                txtOutputFolder.Focus();
                return;
            }

            if (!File.Exists(GlobalOptions.Instance.CSWCommonPath))
            {
                MessageBox.Show("Please set CSW.Framework.Common.csproj location.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                ShowOptionsForm();
                return;
            }

            if (!File.Exists(GlobalOptions.Instance.MSBuildPath))
            {
                MessageBox.Show("Please set the MSBuild location.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
                ShowOptionsForm();
                return;
            }

            Cursor.Current = Cursors.WaitCursor;

            List<string> args = new List<string>();

            args.Add("/server:" + m_Project.Server);
            args.Add("/database:" + m_Project.Database);

            if (AuthenticationMode.SqlServerAuthentication.Equals(m_Project.AuthenticationMode))
            {
                args.Add("/user:" + m_Project.User);
                args.Add("/password:" + m_Project.Password);
            }

            args.Add("/language:C#");

            if (!string.IsNullOrEmpty(m_Project.Namespace))
                args.Add("/namespace:" + m_Project.Namespace);

            if (!string.IsNullOrEmpty(m_Project.DataContextName))
                args.Add("/context:" + m_Project.DataContextName);

            args.Add("/serialization:" + m_Project.SerializationMode.Name);

            if (m_Project.UsePluralize)
                args.Add("/pluralize");

            if (m_Project.GenerateStoredProcedures)
                args.Add("/sprocs");

            if (m_Project.GenerateViews)
                args.Add("/views");

            if (m_Project.GenerateFunctions)
                args.Add("/functions");

            if (!Directory.Exists(m_Project.OutputPath))
                Directory.CreateDirectory(m_Project.OutputPath);

            string dataContextOutputDirectory = Path.Combine(m_Project.OutputPath, m_Project.Namespace);
            if (!Directory.Exists(dataContextOutputDirectory))
                Directory.CreateDirectory(dataContextOutputDirectory);

            string dataContextOutputFile = Path.Combine(dataContextOutputDirectory, m_Project.DataContextName) + ".cs";
            args.Add(string.Format("/code:\"{0}\"", dataContextOutputFile));

            string sqlMetal = GlobalOptions.Instance.SQLMetalPath;

            string arguments = string.Empty;
            foreach (string arg in args)
            {
                if (string.IsNullOrEmpty(arguments))
                    arguments = arg;
                else
                    arguments += " " + arg;
            }

            Process process = new Process();
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardInput = false;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.FileName = sqlMetal;
            process.StartInfo.Arguments = arguments;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();

            process.WaitForExit();

            output += process.StandardOutput.ReadToEnd();

            if (process.ExitCode != 0)
            {
                Cursor.Current = Cursors.Default;
                MessageBox.Show(output, "", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            string solutionFile = SqlMetalModifier.Generate(m_Project.OutputPath, m_Project.SolutionName, dataContextOutputFile, dataContextOutputFile, GlobalOptions.Instance);
            Cursor.Current = Cursors.Default;

            /*process = new Process();
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardInput = false;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.FileName = GlobalOptions.Instance.MSBuildPath;
            process.StartInfo.Arguments = solutionFile;
            process.Start();

            bool success = process.WaitForExit(45000);
            if (!success)
            {
                throw new TimeoutException("msbuild has not exitted in the time allotted");
            }

            string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            string dllFileName = m_Project.Namespace + ".dll";
            string dllPath = Path.Combine(Path.Combine(dataContextOutputDirectory, @"bin\debug"), dllFileName);

            Assembly asm = Assembly.LoadFile(dllPath);

            XElement tablesParentNode = new XElement("tables");

            foreach (Type type in GetTableTypes(asm))
            {
                XElement tableNode = new XElement("table");
                tableNode.Add(new XElement("name", type.Name));

                XElement propertiesNode = new XElement("properties");
                XElement childrenNode = new XElement("children");

                tableNode.Add(propertiesNode);

                tablesParentNode.Add(tableNode);

                Console.WriteLine("============================");
                Console.WriteLine("Table: " + type.Name);
                Console.WriteLine("============================");
                List<string> foreignKeys = new List<string>();
                foreach (System.Reflection.PropertyInfo pi in type.GetProperties())
                {
                    Console.WriteLine("Property: " + pi.Name);
                    //pi.PropertyType
                    object[] attribArray = pi.GetCustomAttributes(false);
                    foreach (object attrib in attribArray)
                    {
                        //Console.WriteLine(attrib.GetType().Name);
                        ColumnAttribute column = attrib as ColumnAttribute;
                        AssociationAttribute assoc = attrib as AssociationAttribute;

                        // EntitySet
                        if (pi.PropertyType.IsGenericType)
                        {
                            Type test = pi.PropertyType.GetGenericTypeDefinition();
                            Type[] test2 = pi.PropertyType.GetGenericArguments();
                            if (test == typeof(System.Data.Linq.EntitySet<>))
                            {
                                if (test2.Length == 1 && assoc != null)
                                {
                                    XElement propertyNode = new XElement("property");
                                    propertiesNode.Add(propertyNode);

                                    propertyNode.Add(new[] {
                                            new XElement("name", pi.Name),
                                            new XElement("is_entity_set", true),
                                            new XElement("is_foreign_key", false),
                                            new XElement("is_entity_ref", false),
                                            new XElement("net_type", test2[0].Name),
                                            new XElement("db_type", string.Empty),
                                            new XElement("is_primary_key", false),
                                            new XElement("can_be_null", true),
                                            new XElement("is_db_generated", false),
                                            new XElement("is_discriminator", false),
                                            new XElement("is_version", false),
                                            new XElement("expression", string.Empty)
                                        });

                                    // OtherKey
                                }
                                else
                                {
                                    throw new InvalidOperationException();
                                }
                            }
                        }

                        if (pi.PropertyType.BaseType != null &&
                            pi.PropertyType.BaseType.Name == "LinqBusinessObject" &&
                            assoc != null)
                        {
                            if (assoc.IsForeignKey)
                            {
                                string[] fkeys = assoc.ThisKey.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                                foreignKeys.AddRange(fkeys);
                            }

                            XElement propertyNode = new XElement("property");
                            propertiesNode.Add(propertyNode);

                            propertyNode.Add(new[] {
                                            new XElement("name", pi.Name),
                                            new XElement("is_entity_set", false),
                                            new XElement("is_foreign_key", assoc.IsForeignKey),
                                            new XElement("is_entity_ref", true),
                                            new XElement("net_type", pi.PropertyType.Name),
                                            new XElement("db_type", string.Empty),
                                            new XElement("is_primary_key", false),
                                            new XElement("can_be_null", true),
                                            new XElement("is_db_generated", false),
                                            new XElement("is_discriminator", false),
                                            new XElement("is_version", false),
                                            new XElement("expression", string.Empty)
                                        });
                        }

                        if (column != null)
                        {
                            string dotNetType = pi.PropertyType.Name;

                            if (pi.PropertyType.IsGenericType)
                            {
                                Type test = pi.PropertyType.GetGenericTypeDefinition();
                                Type[] test2 = pi.PropertyType.GetGenericArguments();
                                if (test == typeof(Nullable<>) && test2.Length == 1)
                                {
                                    dotNetType = string.Format("Nullable<{0}>", test2[0].Name);
                                }
                                else
                                {
                                    throw new InvalidOperationException(string.Format("Type '{0}' unhandled.", pi.PropertyType.FullName));
                                }
                            }

                            XElement propertyNode = new XElement("property");
                            propertiesNode.Add(propertyNode);

                            propertyNode.Add(new[] {
                                new XElement("name", pi.Name),
                                new XElement("is_entity_set", false),
                                new XElement("is_foreign_key", false),
                                new XElement("is_entity_ref", false),
                                new XElement("net_type", dotNetType),
                                new XElement("db_type", column.DbType),
                                new XElement("is_primary_key", column.IsPrimaryKey),
                                new XElement("can_be_null", column.CanBeNull),
                                new XElement("is_db_generated", column.IsDbGenerated),
                                new XElement("is_discriminator", column.IsDiscriminator),
                                new XElement("is_version", column.IsVersion),
                                new XElement("expression", column.Expression)
                            });
                        }
                    }
                }

                IEnumerable<XElement> nodes = propertiesNode.Elements()
                    .Where(p => foreignKeys.Contains(
                        p.Elements().Where(q => q.Name == "name").Single().Value)
                    );

                foreach (XElement item in nodes)
                {
                    item.Add(new XElement("is_deprecated", true));
                }

                IEnumerable<XElement> fknodes = nodes.Select(p => p.Elements().Where(q => q.Name == "is_foreign_key").Single());

                foreach (XElement item in fknodes)
                {
                    item.SetValue(true);
                }
            }

            XElement xml = new XElement("schema");
            xml.Add(tablesParentNode);*/

            /*string uicsprojTemplatePath = Path.Combine(appPath, @"Templates\UI\UI.csproj.xslt");
            XslCompiledTransform uicsproj = new XslCompiledTransform();
            uicsproj.Load(uicsprojTemplatePath);
            using (StreamWriter sw = new StreamWriter(File.))
            {
                uicsproj.Transform(xml.CreateReader(), sw);
            }*/


            /*GenerationCompleteForm form = new GenerationCompleteForm(solutionFile);
            Cursor.Current = Cursors.Default;
            form.Show();*/
        }

        private List<Type> GetTableTypes(Assembly assembly)
        {
            Guard.ArgumentNotNull(assembly, "assembly");

            return assembly.GetTypes().Where(p => p.BaseType.Name == "LinqBusinessObject").ToList();
        }

        private void mnuAbout_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            using (AboutForm form = new AboutForm())
            {
                Cursor.Current = Cursors.Default;
                form.ShowDialog();
            }
        }

        private void mnuOpen_Click(object sender, EventArgs e)
        {
            m_Binder.ValidateAllControls();

            bool cancel;
            AskSaveChanges(out cancel);

            if (!cancel)
            {
                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    LoadProject(openFileDialog.FileName);
                }
            }
        }

        private void mnuSave_Click(object sender, EventArgs e)
        {
            m_Binder.ValidateAllControls();

            Save();
        }

        private bool Save()
        {
            if (string.IsNullOrEmpty(m_Project.ProjectFilePath))
            {
                return SaveAs();
            }
            else
            {
                Cursor.Current = Cursors.WaitCursor;
                m_Project.Save();
                LoadProject(m_Project.ProjectFilePath);
                Cursor.Current = Cursors.Default;
                return true;
            }
        }

        private bool SaveAs()
        {
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
            {
                m_Project.ProjectFilePath = saveFileDialog.FileName;
                return Save();
            }
            else
            {
                return false;
            }
        }

        private void mnuNew_Click(object sender, EventArgs e)
        {
            m_Binder.ValidateAllControls();

            bool cancel;
            AskSaveChanges(out cancel);

            if (!cancel)
            {
                LoadProject(new Project());
            }
        }

        private void AskSaveChanges(out bool cancel)
        {
            cancel = false;

            Application.DoEvents();

            m_Binder.ValidateAllControls();

            if (m_Project.IsEditting &&
                m_Project.ObjectState != BindableObjectState.Pristine)
            {
                DialogResult res = MessageBox.Show("Save changes?", "", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                if (res == DialogResult.Yes)
                {
                    if (!Save())
                        cancel = true;
                }
                else if (res != DialogResult.No)
                {
                    cancel = true;
                }
            }

            Application.DoEvents();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None)
            {
                bool cancel;
                AskSaveChanges(out cancel);
                if (cancel)
                    e.Cancel = true;
            }
        }

        private void mnuExit_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}
